#include "task.hpp"
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
using namespace std;
const int processnum=5;
vector<task_t> tasks;
//先描述
class channel
{
public:
    channel(int cmdfd,int slaverid,string name)
    :_cmdfd(cmdfd),_slaverid(slaverid),_name(name)
    {

    }
public:
    int _cmdfd;//文件描述符
    pid_t _slaverid;//子进程pid
    string _name;//
};
void slaver()
{
    // read(0)
    while(true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int)); // 如果父进程不给子进程发送数据呢？？阻塞等待！
        if(n == sizeof(int))
        {
            //执行cmdcode对应的任务列表
            std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << std::endl;
            if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();
        }
        if(n == 0) break;
    }
}

void InitProcessPool(vector<channel> *channels)//初始化创建子进程池
{
    for(int i=0;i<processnum;i++)
    {
        int pipefd[2];
        int n=pipe(pipefd);
        assert(!n);
        pid_t id=fork();
        if(id==0)
        {
            close(pipefd[1]);
            dup2(pipefd[0],0);
            close(pipefd[0]);
            slaver();
            exit(0);
        }
        //father
        close(pipefd[0]);
        string name = "process-" + std::to_string(i);
        channels->push_back(channel(pipefd[1],id,name));
        sleep(1);
    }
}
void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}
void ctrlSlaver(vector<channel> &channels)//wangguandaoi
{
    int which=0;
    while(true)
    {
        int select=0;
        Menu();
        cin>>select;
        if(select<=0||select>=5) break;
        // select > 0&& select < 5
        // 1. 选择任务
        // int cmdcode = rand()%tasks.size();
        int cmdcode=select-1;
        // 2. 选择进程
        // int processpos = rand()%channels.size();

        std::cout << "father say: " << " cmdcode: " <<
        cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._name << std::endl;
        // 3. 发送任务
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
        which++;
        which %= channels.size();
        // cnt--;
        // sleep(1);
    }
}

int main()
{
    vector<channel> channels;
    srand(time(nullptr)^getpid()^1023);
    // 1. 初始化 --- 
    InitProcessPool(&channels);
    ctrlSlaver(channels);
    
}
//slaver中的read一直在等父进程写入数据，调用ctrlSlaver，父进程再写入这时候read就开始读入了









//你观察到了一个关键细节：子进程的文件描述符 3 虽然数值相同，但它们属于不同的进程，指向不同的内核管道资源。这需要从 **“进程独立性”和“文件描述符表的作用”** 来理解：
// 1. 核心原理：文件描述符是 “进程内的局部索引”
// 文件描述符的本质：
// 文件描述符（如 3）是进程专属的 “资源索引”，只在当前进程的文件描述符表中有效。不同进程的 3，可以指向完全不同的内核资源（如不同管道、不同文件）。
// 父子进程的文件描述符表：
// fork 时，子进程会复制父进程的文件描述符表，但复制出的表项是独立的副本。子进程修改自己的文件描述符表（如关闭、重定向），不会影响父进程的表。
// 2. 分步拆解 “重复数值 3” 的场景
// 假设 processnum=2，循环两次创建管道，看父子进程的文件描述符表变化：
// 第一次循环（创建第一个管道）：
// 父进程调用 pipe(pipefd)，内核分配：
// pipefd[0] = 3（读端，指向管道 A 的读缓冲区）
// pipefd[1] = 4（写端，指向管道 A 的写缓冲区）
// 父进程的文件描述符表：
// plaintext
// 0: 标准输入  
// 1: 标准输出  
// 2: 标准错误  
// 3: 管道 A 读端  
// 4: 管道 A 写端  

// fork 子进程 1，子进程复制父进程的文件描述符表：
// 子进程 1 的文件描述符表（初始与父进程相同）：
// plaintext
// 0: 标准输入  
// 1: 标准输出  
// 2: 标准错误  
// 3: 管道 A 读端（与父进程指向同一内核资源）  
// 4: 管道 A 写端（与父进程指向同一内核资源）  

// 子进程 1 执行：
// close(pipefd[1]) → 关闭自己的 4（写端），子进程 1 的表：
// plaintext
// 4: 已关闭（但父进程的 4 仍有效）  

// dup2(pipefd[0], 0) → 把 3（管道 A 读端）重定向到标准输入 0，子进程 1 的表：
// plaintext
// 0: 指向管道 A 读端（原 3 的资源）  
// 3: 仍指向管道 A 读端（但被重定向后，通常会保留或关闭，取决于代码）  

// 最终，子进程 1 的标准输入 0，绑定管道 A 的读端。
// 父进程执行：
// close(pipefd[0]) → 关闭自己的 3（读端），父进程的表：
// plaintext
// 3: 已关闭  

// 父进程保留 4（管道 A 写端），后续可向管道 A 写数据，子进程 1 能从标准输入 0 读到。
// 第二次循环（创建第二个管道）：
// 父进程再次调用 pipe(pipefd)，内核分配新的管道 B：
// 父进程当前最小可用文件描述符是 3（因为第一次循环中父进程关闭了 3），所以：
// pipefd[0] = 3（读端，指向管道 B 的读缓冲区）
// pipefd[1] = 5（写端，指向管道 B 的写缓冲区）
// 父进程的文件描述符表：
// plaintext
// 3: 管道 B 读端  
// 4: 管道 A 写端  
// 5: 管道 B 写端  

// fork 子进程 2，子进程复制父进程的文件描述符表：
// 子进程 2 的文件描述符表（初始）：
// plaintext
// 0: 标准输入  
// 1: 标准输出  
// 2: 标准错误  
// 3: 管道 B 读端（指向管道 B 的读缓冲区）  
// 4: 管道 A 写端（但子进程 2 会关闭它，因为 `oldfds` 包含）  
// 5: 管道 B 写端（指向管道 B 的写缓冲区）  

// 子进程 2 执行：
// 遍历 oldfds（包含第一次循环的 4），执行 close(4) → 关闭自己的 4（管道 A 写端，不影响父进程）。
// close(pipefd[1]) → 关闭自己的 5（管道 B 写端），子进程 2 的表：
// plaintext
// 5: 已关闭  

// dup2(pipefd[0], 0) → 把 3（管道 B 读端）重定向到标准输入 0，子进程 2 的表：
// plaintext
// 0: 指向管道 B 读端（原 3 的资源）  

// 最终，子进程 2 的标准输入 0，绑定管道 B 的读端。
// 父进程执行：
// close(pipefd[0]) → 关闭自己的 3（管道 B 读端），父进程的表：
// plaintext
// 3: 已关闭  

// 父进程保留 5（管道 B 写端），后续可向管道 B 写数据，子进程 2 能从标准输入 0 读到。
// 3. 为什么 “数值相同但不影响一对一”？
// 内核资源独立：
// 第一次循环的 3 指向管道 A，第二次循环的 3 指向管道 B。虽然数值都是 3，但它们在不同进程的文件描述符表中，指向不同的内核管道资源。
// 进程独立性：
// 子进程 1 的 3 属于子进程 1 的文件描述符表，子进程 2 的 3 属于子进程 2 的文件描述符表。两个 3 互不干扰，分别对应管道 A 和管道 B。
// 数据流向明确：
// 父进程向管道 A 写端（4）发数据 → 子进程 1 从标准输入 0（绑定管道 A 读端）读数据。
// 父进程向管道 B 写端（5）发数据 → 子进程 2 从标准输入 0（绑定管道 B 读端）读数据。
// 即使子进程的标准输入 0 数值相同，但背后绑定的内核管道不同，数据流向完全独立。
// 4. 总结：“数值相同” 不影响 “一对一” 的本质
// 文件描述符的数值是进程内的局部索引，不同进程的相同数值可以指向不同内核资源。只要内核资源（管道）独立，即使文件描述符数值重复，也能实现精准的 “一对一” 通信。
// 这正是进程的 “独立性” 和 “文件描述符表复制机制” 的巧妙设计：数值可复用，但资源独立，既节省了描述符编号，又保证了通信的精准性。